//#define PROFILEINDENT

//
// CODEBOXES
// This implements a vaguely EMACS-like code editor window using the RichTextBox class.
// It supports emacs-like regions as first-class objects (see Region.cs), however, these
// are implemented internally using the RichTextBox selection operations, so please
// don't screw with those directly.
//

//
// TODO:
//
// - Make undo less eggregiously inefficient
// - Save style info in the source files
// - Make printing handle rich text
//
// BUGS:
//

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Globalization;
using System.Xml;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
using Meta;

namespace Meta.Editor {
	public delegate void Thunk();

	public class CodeBox : ExRichTextBox {
		/// <summary>
		/// Static members
		/// </summary>
		[DllImport("user32.dll")]
		private static extern bool LockWindowUpdate(int hWnd);

#if OLD_SCROLLHACKING
		// Alas, none of this seems to have any effect
		[DllImport("user32.dll")]
		public static extern int GetScrollPos(int hWnd, int nBar);
		[DllImport("user32.dll")]
		public static extern int SetScrollPos(int hWnd, int nBar, int nPos, bool redraw);
		// From PlatformSDK/Include/WinUser.h
		public const int SB_HORZ = 0;
		public const int SB_VERT = 1;
#endif

		public event Thunk VerticalPositionChanged;
		public event Thunk SpacePressed;

		// Stylistic parameters
		static ColorConverter colorConverter = new ColorConverter();

		// Lexical and syntactic constants
		public const char startOfCommentChar = '\u00ab';
		public const char endOfCommentChar = '\u00bb';
		public const string startOfCommentString = "\u00ab";
		public const string endOfCommentString = "\u00bb";
		public static readonly char[] commentChars = new Char[2] { '\u00ab', '\u00bb' };

		// Key substititions (e.g. changing '-' and '>' into the lambda arrow
		static Hashtable keySubstitutions = new Hashtable();

		public static int highlightPeriod = 500;
		public static int reindentTimeout = 2000;

		/// <summary>
		/// Instance memebers
		/// </summary>
		/// 

		System.ComponentModel.IContainer components = null;

		// Command-processing state variables
		bool greekNext;                            // Next char should be treated as a Greek letter
		public bool iSearchMode=false;               // In i-search mode
		int iSearchStartPoint;
		string iSearchString;

		Style nextStyle;

		// Parsing-related state variables
		Region currentTopLevelRegion = new Region(0,0);
		internal bool reIndent = true;
		public bool undoEnabled = true;

#if SYNTAXHIGHLIGHTING
		bool reComment;                          // Rerun the commenting code code over this line (I think -ian)
#endif
		System.Windows.Forms.Timer unhighlightTimer;
		System.Windows.Forms.Timer reindentTimer;
		public ToolTip tooltip;

		// Other state variables
		System.Collections.Stack undoStack = new System.Collections.Stack();
		bool ModifiedSinceUndoMark = false;

		public Editor EditorWindow {
			get {
				return FindForm() as Editor;
			}
		}

		public void SetStatusLine(string str) {
			EditorWindow.SetArglistText(str);
		}

		public Namespace Namespace {
			get {
				return EditorWindow.Namespace;
			}
		}

		/// <summary>
		/// Constructors
		/// </summary>
		public CodeBox() {
			// This call is required by the Windows Form Designer.
			InitializeComponent();
			
			AcceptsTab = true;
			DetectUrls = true;
			AutoWordSelection = false;   // It would be nice to have this, but it often triggers accidentally and it also tends to include brackets in the word.
			AllowDrop = true;

			foreach (DictionaryEntry e in defaultStyleTable)
				RegisterStyle((string)e.Key, (Style)e.Value);
			Font = TheStyle("Normal").font;
			
			TabIndex = 4;
			Text = "";
			WordWrap = false;
			unhighlightTimer = new System.Windows.Forms.Timer();
			unhighlightTimer.Stop();
			unhighlightTimer.Tick += new EventHandler(Unhighlight);
			unhighlightTimer.Interval = highlightPeriod;
			reindentTimer = new System.Windows.Forms.Timer();
			reindentTimer.Stop();
			reindentTimer.Tick += new EventHandler(ReindentHandler);
			reindentTimer.Interval = reindentTimeout;
			tooltip=new ToolTip();
			tooltip.InitialDelay=3000;
			tooltip.AutoPopDelay=3000;
			tooltip.ReshowDelay=3000;
			//tooltip.AutomaticDelay = 1000;
			tooltip.ShowAlways = false;

#if SYNTAXHIGHLIGHTING
			BackColor = EditorConfiguration.backColor;
			ForeColor = EditorConfiguration.foreColor;
#else
			BackColor = Editor.defaultBackgroundColor;
#endif
			//VerticalPositionChanged += new Thunk(ReindentCurrentLine);
			VerticalPositionChanged += new Thunk(IndentTopLevelExpression);
			TextChanged += new EventHandler(HandleTextChanged);
		}

		static CodeBox() {
			DefineKeySubstitution("<-", "\u2190");
			DefineKeySubstitution("<=", "\u2264");
			DefineKeySubstitution(">=", "\u2265"); 
			DefineKeySubstitution("<<", startOfCommentString); 
			DefineKeySubstitution(">>", endOfCommentString); 
			DefineKeySubstitution("->", "\u2192");
			DefineKeySubstitution("\u2212>", "\u2192");
			DefineKeySubstitution("/=", "\u2260");
			DefineKeySubstitution("=/", "\u2260");
			DefineKeySubstitution("!=", "\u2260");
			DefineKeySubstitution("sqrt", "\u221a");
			DefineKeySubstitution("==", "\u2261");
			//DefineKeySubstitution("/==", "\u2262");
			//DefineKeySubstitution("*=", "\u225b");
			//DefineKeySubstitution("\u00d7=", "\u225b");
			DefineKeySubstitution("~~", "\u2248");
			DefineKeySubstitution("infinity", "\u221e");
			DefineKeySubstitution("<>", "\u25ca");
			defaultStyleTable["Normal"] = Editor.defaultNormalStyle;
			defaultStyleTable["Title"] = Editor.defaultTitleStyle;
			defaultStyleTable["Comment"] = Editor.defaultCommentStyle;
			ReadDefaults();
		}

		public void RecordDefaults() {
			Editor.defaultBackgroundColor = BackColor;
			defaultStyleTable.Clear();
			foreach (DictionaryEntry e in styleTable)
				defaultStyleTable[e.Key] = e.Value;
			SaveDefaults();
		}

		static string defaultsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData)
			+ Path.DirectorySeparatorChar + "Meta" + Path.DirectorySeparatorChar + "EditorConfig";
		static void SaveDefaults() {
			Color back = Editor.defaultBackgroundColor;
			object[] styles = new Object[defaultStyleTable.Count+1];
			styles[0] = "styles";
			int i=1;
			foreach (DictionaryEntry e in defaultStyleTable) {
				object[] style = new Object[7];
				style[0] = e.Key;
				Style s = (Style)e.Value;
				Font f = s.font;
				Color c = s.color;
				style[1] = f.Name;
				style[2] = f.Size;
				style[3] = (int)f.Style;
				style[4] = c.R;
				style[5] = c.G;
				style[6] = c.B;
				styles[i++] = style;
			}
			object[] configuration = new object[] { new object[4] { "color", back.R, back.G, back.B },
													  styles,
													  new object[] { "OptimizeTailCalls", Expression.OptimizeTailCalls.Value},
													  new object[] {"ShowAllExpressions", EvalStack.showAllExpressions}, 
													  new object[] {"ShowDetail", Inspector.showDetail},
													  new object[] {"FocusOnWrite", Listener.focusOnWrite},
													  new object[] {"InsertMatchingBrackets", Editor.insertMatchingBrackets},
													  //													  new object[] {"IndentDefsLispStyle", Editor.indentDefsLispStyle},
													  new object[] {"FastEditorMode", Editor.fastMode},
													  new object[] {"ImportBasic2D", Meta.TopLevel.AlwaysOpen2D}
												  };

			string dir = Path.GetDirectoryName(defaultsPath);
			if (!System.IO.Directory.Exists(dir))
				System.IO.Directory.CreateDirectory(dir);

			StreamWriter file = new StreamWriter(defaultsPath);
			Writer t = new Writer(file);
			t.Write(configuration);
			file.Close();
		}

		static void ReadDefaults() {
			//try {
			//string defaultsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData)
			//	+ Path.DirectorySeparatorChar + "Meta" + Path.DirectorySeparatorChar + "EditorConfig";

			if (System.IO.Directory.Exists(Path.GetDirectoryName(defaultsPath)) && System.IO.File.Exists(defaultsPath)) {
				StreamReader file = new StreamReader(defaultsPath);
				Reader r = new Reader(file, 0);
				object[] configuration = (object[])r.Read();
				foreach (object[] item in configuration) {
					string type = (string)item[0];
					switch (type) {
						case "styles":
							defaultStyleTable.Clear();
							for (int i=1;i<item.Length; i++) {
								object[] description = (object[])item[i];
								string styleName = (string)description[0];
								string fontName = (string)description[1];
								float fontSize = Convert.ToSingle(description[2]);
								FontStyle fs = (FontStyle)Convert.ToInt32(description[3]);
								Color c = Color.FromArgb(255, (int)description[4], (int)description[5], (int)description[6]);
								defaultStyleTable[styleName] = new Style(new Font(fontName, fontSize, fs), c);
							}
							break;

						case "color":
							Editor.defaultBackgroundColor = Color.FromArgb(255, (int)item[1], (int)item[2], (int)item[3]);
							break;

						case "OptimizeTailCalls":
							Expression.OptimizeTailCalls.DefaultValue = item[1];
							break;

						case "ShowAllExpressions":
							EvalStack.showAllExpressions = (bool)item[1];
							break;

						case "ShowDetail":
							Inspector.showDetail = (bool)item[1];
							break;

						case "FocusOnWrite":
							Listener.focusOnWrite = (bool)item[1];
							break;

						case "InsertMatchingBrackets":
							Editor.insertMatchingBrackets = (bool)item[1];
							break;

							//							case "IndentDefsLispStyle":
							//								Editor.indentDefsLispStyle = (bool)item[1];
							//								break;

						case "FastEditorMode":
							Editor.fastMode = (bool)item[1];
							break;

						case "ImportBasic2D":
							TopLevel.AlwaysOpen2D = (bool)item[1];
							break;
					}
				}
				file.Close();
			}
			//} catch (Exception e) {
			//	MessageBox.Show("Error reading editor configuration file", e.Message);
			//}
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing ) {
			// This is incredibly stupid, but the native dispose method actually calls the
			// convert-to-rtf code to serialize it into an in-core string.  This can take hundreds
			// of megs of virtual memory when we dispose a Listener window with lots of
			// images.
			Clear();
			// It's not good if these go off after the buffer is closed...
			unhighlightTimer.Stop();
			reindentTimer.Stop();
			if( disposing ) {
				if (components != null) {
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		public bool autoStyleTitles = true;
		public bool autoStyleComments = true;
		public class Style {
			public Font font;
			public Color color;

			public Style(Font font, Color color) {
				this.font = font;
				this.color = color;
			}

			public Style(string fontFamilyName, float fontSize, FontStyle fontStyle, Color color) {
				font = new Font(fontFamilyName, fontSize, fontStyle);
				this.color = color;
			}
		}

		static Hashtable defaultStyleTable = new Hashtable();
		Hashtable styleTable = new Hashtable();
		public Style TheStyle(string name) {
			return styleTable[name] as Style;
		}
		public void RegisterStyle(string name, Style s) {
			styleTable[name] = s;
		}
		
		//
		// Key substition (for handling multi-key combinations like ->)
		//

		class SubstitutionInfo {
			public string prefix;
			public string rewrite;
			public SubstitutionInfo(string prefix, string rewrite) {
				this.prefix = prefix;
				this.rewrite = rewrite;
			}
		}

		public static void DefineKeySubstitution(string from, string to) {
			char lastChar = from[from.Length-1];
			string prefix = from.Substring(0, from.Length-1);

			object probe = keySubstitutions[lastChar];
			ArrayList subs = (probe==null)?new ArrayList():(ArrayList)probe;

			subs.Add(new SubstitutionInfo(prefix, to));
			if (probe==null)
				keySubstitutions[lastChar] = subs;
		}


		//
		// Check if key is the second key of a two-key sequence and handle it
		// Returns true if it handled the event.
		//
		bool MaybeHandleCharacterSubstitution(char key) {
			object probe = keySubstitutions[key];

			// Easy cases are table-driven
			if (probe != null)
				foreach (SubstitutionInfo subst in (ArrayList)probe)
					if (MaybeChangeChar(subst.prefix, subst.rewrite))
						return true;

			// Harder cases require looking at context.
			switch (key) {
				case '-':
					return MaybeInsertOperator(Meta.TopLevel.minusSymbol);

				case '*':
					return MaybeInsertOperator(Meta.TopLevel.timesSymbol);

				case '/':
					return MaybeInsertOperator(" "+Meta.TopLevel.divisionSymbol+" ");

				case '"':
					// We don't know whether to insert an open quote or a close quote so we have to check the context.
					if (Editor.insertMatchingBrackets) {
						SetRegionText(userSelection, "\u201c\u201d");
					} else {
						CodeScanner s=ScanCharsFrom(userSelection.start);
						SetRegionText(userSelection,
							new string(s.FindCorrectQuoteMark('\u201c'),1));
					}
					userSelection.start += 1;
					userSelection.length = 0;
					return true;
			}

			object matching = CodeScanner.matchingBracket[key];
			if (Editor.insertMatchingBrackets && matching!=null
				&& (char.GetUnicodeCategory(key)==UnicodeCategory.OpenPunctuation
				|| char.GetUnicodeCategory(key)==UnicodeCategory.InitialQuotePunctuation)) {
				SetRegionText(userSelection, new string(new char[] {key, (char) matching}));
				userSelection.start += 1;
				userSelection.length = 0;

				return true;
			}

			return false;
		}


		[DllImport("user32.dll", EntryPoint="SendMessage", CharSet=CharSet.Auto)] 
		private static extern IntPtr SendMessageRedraw(IntPtr hWnd, Int32 msg, Int32 wParam, Int32 lParam);

		private const int WM_SETREDRAW = 0xB; 
		void SetRedraw(bool redrawEnabled) {
			SendMessageRedraw(Handle, WM_SETREDRAW, Convert.ToInt32(redrawEnabled), 0);
		}

		int lockCount=0;
		public void LockWindow() {
			if (lockCount==0) {
				//LockWindowUpdate((int)this.Handle);
				SetRedraw(false);
			}
			lockCount++;
		}

		public void AssertNotMultiplyLocked() {
			Debug.Assert(lockCount<2, "Window multiply locked when it shouldn't be");
		}

		public void MaybeUnlockWindow() {
			MaybeUnlockWindow(true);
		}

		public void MaybeUnlockWindow(bool checkStillLocked) {
			if (WindowLocked)
				UnlockWindow();
			Debug.Assert(!checkStillLocked || !WindowLocked, "MaybeUnlockWindow used when window multiply locked");
		}
		public void UnlockWindow() {
			System.Diagnostics.Debug.Assert(lockCount>0, "Unlocking buffer that's already unlocked!");
			lockCount--;
			if (lockCount==0) {
				//LockWindowUpdate(0);
				SetRedraw(true);
				Invalidate();
			}
		}

		public void UnlockWindowDammit() {
			if (lockCount!=0) {
				lockCount = 0;
				SetRedraw(true);
				Invalidate();
			}
		}

		public bool WindowLocked {
			get {
				return lockCount>0;
			}
		}

		char ReorderForGreek(char c) {
			switch (c) {
				case 'a':	  // alpha
				case 'b':  // beta
				case 'd':  // delta
				case 'e':  // epsilon
				case 'i':   // iota
				case 'o':  // omicron
				case 'p':  // pi
				case 's':  // sigma
				case 't':   // tau
				case 'u': // phi
					return c;

				case 'g':   // gamma
					return 'c';
				case 'z':  // zeta/Z
					return 'f';
				case 'h':  // Eta/H
					return 'g';
				case 'j':   // theta
					return 'h';
				case 'k':  // kappa
					return 'j';
				case 'l':   // lambda
					return 'k';
				case 'm':  // mu
					return 'l';
				case 'n':  // nu
					return 'm';
				case 'r':  // rho
					return 'q';
				case 'q':  // sigma final
					return 'r';
				case 'w':
					return 'y';
				case 'x': // chi
					return 'w';
				case 'y': // psi
					return 'x';
				
				default:
					return (char)0;
			}
		}

		char ToGreek(char c) {
			char reordered = ReorderForGreek(Char.ToLower(c));
			if (reordered==0)
				return c;
			else if (c>='A' && c<='Z')
				return (char)(reordered-'a'+'\u0391');
			else if (c>='a' && c<='z')
				return (char)(reordered-'a'+'\u03b1');
			else
				return c;
		}

		ArrayList completionResults = new ArrayList();
		string currentCompletion = null;
		public void TryCompletion(string prefix, string name) {
			if (name.StartsWith(prefix)) {
				if (!completionResults.Contains(name))
					completionResults.Add(name);
				if (currentCompletion==null)
					currentCompletion=name;
				else {
					// need to shorten the completion
					for (int i=0; i<Math.Min(name.Length,currentCompletion.Length); i++)
						if (name[i]!=currentCompletion[i]) {
							// Found where they diverge
							currentCompletion=currentCompletion.Substring(0, i);
						}
					if (name.Length<currentCompletion.Length)
						// If we get here, then name must be a prefix of currentCompletion
						currentCompletion=name;
				}
			}
		}

		protected void TryBindings(string prefix, IDictionary bindings) {
			if (bindings==null)
				return;
			foreach (DictionaryEntry e in bindings) {
				Symbol s=e.Key as Symbol;
				if (s!=null)
					TryCompletion(prefix, s.name);
				else
					TryCompletion(prefix, ((GlobalVariable)e.Key).name.name);
			}
		}

		protected void TryMembers(string prefix, Type type) {
			MemberInfo[] members = type.GetMembers();
			if (members!=null)
				foreach (MemberInfo m in members) {
					MethodInfo meth = m as MethodInfo;
					ConstructorInfo con = m as ConstructorInfo;

					if (con==null && (meth==null || !meth.IsSpecialName))
						TryCompletion(prefix, m.Name);
				}
		}

		protected string CompleteSymbol(string prefix) {
			currentCompletion=null;
			TryBindings(prefix, Namespace.bindings);
			foreach (NamespaceBase n in this.Namespace.Imports)
				TryBindings(prefix, n.Exports);
			CompletionWalker.WalkCompletions(this, TopLevelExpressionRegion(userSelection.Start).Start, prefix);
			return (currentCompletion!=null)?currentCompletion:prefix;
		}

		public void BreakStringAtLastDot(string source, out string prefix, out string suffix) {
			int lastdot = source.LastIndexOf('.');
			if (lastdot==source.Length) {
				prefix=source.Substring(source.Length-1);
				suffix="";
			} else {
				prefix=source.Substring(0, lastdot);
				suffix=source.Substring(lastdot+1, source.Length-(lastdot+1));
			}
		}

		public object LookupMemberExpression(string e) {
			if (e.IndexOf(".")>=0) {
				string objectExpression;
				string memberName;
				BreakStringAtLastDot(e, out objectExpression, out memberName);
				object o=LookupMemberExpression(objectExpression);
				if (o is NamespaceBase)
					return ((NamespaceBase)o).CheckBinding(memberName);
				else if (o != null) {
					object result = null;
					try {
						MemberInfo[] m = o.GetType().GetMember(memberName);
						if (m!=null && m.Length==1) {
							if (m[0] is FieldInfo)
								result = ((FieldInfo)m[0]).GetValue(o);
							else if (m[0] is PropertyInfo)
								result = ((PropertyInfo)m[0]).GetValue(o, new object[0]);
						} 
					} catch {}
					return result;
				} else
					return Namespace.CheckBinding(e);
			}
			return Namespace.CheckBinding(e);
		}

		protected string CompleteMember(string prefix) {
			string objectExpression;
			string memberPrefix;
			BreakStringAtLastDot(prefix, out objectExpression, out memberPrefix);
			Type guessedType = TypeGuesser.GuessType(this, TopLevelExpressionRegion(userSelection.start).start, objectExpression);
			currentCompletion=null;
			if (guessedType==null) {
				object memberObject=LookupMemberExpression(objectExpression);
				if (memberObject!=null) {
					if (memberObject is NamespaceBase)
						TryBindings(memberPrefix, ((NamespaceBase)memberObject).Exports);
					else {
						if (memberObject is Type)
							TryMembers(memberPrefix, (Type)memberObject);
						if (completionResults.Count>0)
							completionResults.Add(null);
						TryMembers(memberPrefix, memberObject.GetType());
					}
				}
			} else {
				TryMembers(memberPrefix, guessedType);
			}
			return objectExpression+'.'+((currentCompletion==null)?memberPrefix:currentCompletion);
		}

		void CompleteFromMenuItem(object sender, EventArgs e) {
			string prefix=CurrentVariableNameOrMemberReference;
			int lastDot=prefix.LastIndexOf('.');
			if (lastDot>=0)
				prefix = prefix.Substring(lastDot+1, prefix.Length-(lastDot+1));
			MenuItem m=(MenuItem)sender;
			string completion=(m.Text==prefix)?m.Text:(m.Text.Substring(0,prefix.Length)+m.Text.Substring(prefix.Length+1, m.Text.Length-(prefix.Length+1)));

			if (completion.StartsWith(prefix)) {
				NoteUserSelection();
				SetRegionText(userSelection, completion.Substring(prefix.Length, completion.Length-prefix.Length));
			}
		}

		void Complete() {
			NoteUserSelection();
			UnlockWindowDammit();
			completionResults.Clear();
			string prefix=CurrentVariableNameOrMemberReference;
			currentCompletion = null;
			string completion=prefix;
			if (prefix!="") {
				if (prefix.IndexOf('.')>=0)
					completion = CompleteMember(prefix);
				else
					completion=CompleteSymbol(prefix);
				if (completion!=prefix)
					SetRegionText(userSelection, completion.Substring(prefix.Length, completion.Length-prefix.Length));
				else if (completionResults.Count>0) {
					int sortStart=0;
					while (sortStart<completionResults.Count) {
						// Find separator, if any
						int sortEnd=completionResults.IndexOf(null, sortStart);
						if (sortEnd<0)
							sortEnd = completionResults.Count;
						completionResults.Sort(sortStart, sortEnd-sortStart, System.Collections.Comparer.Default);
						sortStart = sortEnd+1;
					}
					EventHandler completer=new EventHandler(CompleteFromMenuItem);
					ContextMenu m=new ContextMenu();
					int lastDot=prefix.LastIndexOf('.');
					if (lastDot>=0)
						prefix = prefix.Substring(lastDot+1, prefix.Length-(lastDot+1));
					foreach (object o in completionResults) {
						string s=(o!=null)?(string)o:null;
						MenuItem i = new MenuItem();
						if (o==null)
							i.Text="-";
						else if (s==prefix)
							i.Text=s;
						else {
							string start=s.Substring(0,prefix.Length);
							string end=s.Substring(prefix.Length,s.Length-(prefix.Length));
							i.Text = start+"&"+end;
						}
						i.Click += completer;
						m.MenuItems.Add(i);
					}
					m.Show(this, GetPositionFromCharIndex(userSelection.start));
				}
			}
		}
		
		//
		// Principle routine for handling typing.  Called after HandleKeyDown and before HandleKeyUp.
		// Also called before the base class gets to process the event.  Setting e.Handled to true
		// will prevent the base class from handling the key press.
		//
		protected override void OnKeyPress(KeyPressEventArgs e) {
			// Interesting.  This gets called *after* the low level COM object has *already* handled the keypress.  Foo.
			NoteUserSelection();
			ResetCommandPasting();
			if (iSearchMode) {
				DoISearch(e);
				e.Handled = true;
				return;
			}
			Unhighlight();
			if (userSelection.length>0 || e.KeyChar==' ' || e.KeyChar=='\r')
				MarkUndo();

			if (greekNext) {
				char newChar = ToGreek(e.KeyChar);
				SetRegionText(userSelection, new String(newChar,1));
				e.Handled = true;
				greekNext = false;
			} else
				greekNext = false;

			if (e.KeyChar==' ' && SpacePressed != null)
				SpacePressed();

			e.Handled = e.Handled || MaybeHandleCommandKey(e.KeyChar) || MaybeHandleCharacterSubstitution(e.KeyChar);
			
			UpdateTextStyles(e.KeyChar);
			RestoreUserSelection();
			if (!Char.IsControl(e.KeyChar) && WindowLocked)
				// Things feel sluggish if you don't see the char appear on the screen until
				// you let go of the key, so unlock the window right away if it's a printable character.
				UnlockWindow();
			AssertNotMultiplyLocked();
			return;
		}

		//
		// Check if key is a command and handle it.
		// Returns true if it's handled the event.
		//
		bool MaybeHandleCommandKey(char key) {
			// NB: Always returns true if this switch matches something.
			switch (key) {
					// Control-G: Greek mode
				case (char)7:
					greekNext=true;
					break;

					// Control-R: Ignore (triggers right justify mode, otherwise)
				case (char)18:
					break;

				case '\t':
					Complete();
					break;

				default:
					return false;
			}

			return true;
		}

		public void StartISearch() {
			iSearchMode=true;
			iSearchStartPoint=userSelection.start;
			iSearchString="";
			MaybeUnlockWindow();
			UpdateISearchStatus();
		}

		public void EndISearch() {
			if (iSearchMode)
				iSearchMode = false;
			SetStatusLine("");
		}

		public void ISearchNext() {
			NoteUserSelection();
			iSearchStartPoint = userSelection.start+iSearchString.Length;
			UpdateISearchStatus();
		}

		void UpdateISearchStatus() {
			if (iSearchString.Length==0) {
				userSelection=new Region(iSearchStartPoint,0);
				RestoreUserSelection();
				SetStatusLine("isearch (esc to quit): ");
			} else {
				int match = Find(iSearchString, iSearchStartPoint, RichTextBoxFinds.None);
				SetStatusLine("isearch (esc to quit): "+iSearchString+((match>=0)?"":"  not found!"));
			}
		}

		void DoISearch(KeyPressEventArgs e) {
			switch (e.KeyChar) {
				case (char)8:  // backspace
					if (iSearchString.Length>0)
						iSearchString = iSearchString.Substring(0, iSearchString.Length-1);
					UpdateISearchStatus();
					break;

				case (char)27:  // Escape
				case (char)13:  // Return
					EndISearch();
					break;

				default:
					iSearchString = iSearchString+e.KeyChar;
					UpdateISearchStatus();
					break;
			}
		}

		Keys currentModifiers;
		bool isDeleting = false;  // Used to track runs of backspaces for saving undo state.
		protected override void OnKeyDown(KeyEventArgs e) {
			currentModifiers = e.Modifiers;
			//This turns out to be a bad thing to check for, since it can happen in certain rollover situations with ESC.
			//Debug.Assert(!WindowLocked, "Window locked on entry to OnKeyDown");
			UnlockWindowDammit();

			// First check if it's the Isearch key
			if (iSearchMode) {
				switch (e.KeyCode) {
					case Keys.F3:
						ISearchNext();
						break;

					case Keys.Up:
					case Keys.Down:
					case Keys.Left:
					case Keys.Right:
						EndISearch();
						break;
				}
				e.Handled = true;
				AssertNotMultiplyLocked();
				return;
			} 
			//			else if (e.KeyCode == Keys.F3) {
			//				StartISearch();
			//				e.Handled =true;
			//				return;
			//			}

			// It turns out we need this to unhighlight before the base class keypress method gets called.
			// Putting it in OnKeyPress doesn't help because the low level COM object is sometimes
			// trapping the event before we get it.  Or at least that's what it seems like.
			if (e.KeyCode==Keys.LControlKey || e.KeyCode==Keys.RControlKey || e.KeyCode==Keys.ControlKey || e.KeyCode==Keys.ShiftKey || 
				e.KeyCode==Keys.LShiftKey || e.KeyCode==Keys.LShiftKey || e.KeyCode==Keys.Alt || e.KeyCode==Keys.Menu) {
				//AssertNotMultiplyLocked();
				//MaybeUnlockWindow();
				UnlockWindowDammit();
				return;
			}
			LockWindow();
			NoteUserSelection();
			Unhighlight();
#if SYNTAXHIGHLIGHTING
			Keys code = e.KeyCode;
			switch (e.KeyCode) {
				case Keys.Back:
				case Keys.Delete:
					if (EditorConfiguration.doComment)
						reComment =  IsTypingAfter("\u00ab") | IsTypingAfter("\u00bb")	| RegionText(userSelection).IndexOfAny(commentChars)>=0;
					break;
			}	
#endif
			// Mark Undo if necessary
			if (e.KeyCode==Keys.Back || e.KeyCode==Keys.Delete) {
				if (!isDeleting) {
					isDeleting=true;
					MarkUndo();
				}
			} else {
				if (isDeleting) {
					isDeleting=false;
					MarkUndo();
				}
			}

			switch (e.KeyCode) {
				case Keys.Up:
				case Keys.Down:
				case Keys.Left:
				case Keys.Right:
					MarkUndo();
					break;

				default:
					// Do nothing
					break;
			}
			RestoreUserSelection();
			if (!e.Control && (e.KeyCode==Keys.Up || e.KeyCode==Keys.Down
				|| e.KeyCode==Keys.Left || e.KeyCode==Keys.Right ||
				e.KeyCode==Keys.Back || e.KeyCode==Keys.Delete ||
				e.KeyCode==Keys.Space))

				UnlockWindowDammit();
			AssertNotMultiplyLocked();
		}

		protected override void OnMouseDown(MouseEventArgs e) {
			EndISearch();
			MarkUndo();
			NoteUserSelection();
			LockWindow();
			VerticalPositionChanged();
			RestoreUserSelection();
			MaybeHighlightExpression();
			UnlockWindowDammit();
#if DEBUGMOUSECLICK
			EditorWindow.SetArglistText(TopLevelExpressionRegion(userSelection.start).ToString());
#endif
		}

		protected override void OnMouseUp(MouseEventArgs e) {
			UnlockWindowDammit();
			base.OnMouseUp (e);
		}

		protected override void OnGotFocus(EventArgs e) {
			UnlockWindowDammit();
			base.OnGotFocus (e);
		}

		protected override void OnMouseMove(MouseEventArgs e) {
			string tip="";
			int index=GetCharIndexFromPosition(new Point(e.X,e.Y));
			if (index>0 && index<TextLength) {
				switch (currentModifiers) {
					case Keys.Control | Keys.Shift:
						// Display the expression object for the current mouse location.
						Expression exp = EditorWindow.ExpressionAtIncludingVariable(index);
						if (exp!=null)
							tip = Writer.SafeWriteToString(exp, 100);
						//if (exp!=null && exp.InferredType!=null)
						//	tip = exp.InferredType.ToString();
						break;

					case Keys.Control:
						// Display the type for the expression the mouse is over.  Turns out not to be too useful,
						// since the ExpressionAt can't locate variable references, and they're what you really want
						// to find the types of.
						Expression texp = EditorWindow.ExpressionAtIncludingVariable(index);

						if (texp!=null) {
							Type t = texp.InferredType;
							if (t==null)
								t = texp.DeclaredType;
							if (t!=null) {
								if (t is ITypeExpression)
									tip = t.ToString();
								else
									tip = t.Name;
							}
						}
						break;

					default:
						// Display documentation for current symbol, if any
						CodeScanner s=ScanCharsFrom(index);
						int start=s.SkipBackwardToken();
						if (start>=0) {
							int end=s.SkipForwardToken();
							if (end<TextLength) {
								string maybeVar=RegionText(new Region(start, end-start));
								object b=Namespace.CheckBinding(maybeVar);
								if (b!=null) {
									tip = Utilities.Documentation(b);
									if (tip==null)
										tip = b.ToString();
								}
							}
						}
						break;
				}
			}
			tooltip.SetToolTip(this,tip);
			base.OnMouseMove (e);
		}


		static int suppressKeyUp=-1;
		public static void SuppressKeyUp(Keys c) {
			suppressKeyUp = (int)c;
		}

		// On entry to OnKeyUp, the window may still be locked, so we have to be sure to
		// unlock it before returning.
		protected override void OnKeyUp(KeyEventArgs e) {
			currentModifiers = e.Modifiers;
			if (iSearchMode) {
				e.Handled = true;
				// Search mode doesn't lock, so we don't need to unlock.
				return;
			}

			//if (!e.Control || (e.KeyCode!=Keys.Up && e.KeyCode != Keys.Down))
			//	ResetCommandPasting();

			if ((suppressKeyUp>=0 && (int)e.KeyCode==suppressKeyUp) || 
				// The Inspector can't tell us about this because it's in an assembly that can't import from us, so we hardcode here.
				(e.Control && (e.KeyCode==Keys.D1 || e.KeyCode==Keys.E || e.KeyCode==Keys.D))) {
				// Kluge: we get the keyup event with the user types control-d at the inspector
				// to pop up source code.  So we need to suppress processing of the keyup
				// to keep from turning off the highlighting.
				UnlockWindowDammit();
				return;
			}
			suppressKeyUp=-1;

			if (e.KeyCode==Keys.Menu || e.KeyCode ==Keys.ControlKey || e.KeyCode == Keys.ShiftKey) {
				// Don't mess with the screen if it's just the user pushing some modifier key.
				UnlockWindowDammit();
				return;
			}
			if (nextStyle != null) {
				SetRegionStyle(userSelection, nextStyle);
				nextStyle=null;
			}
			NoteUserSelection();
			Unhighlight();

#if SYNTAXHIGHLIGHT			
			if (EditorConfiguration.doHighlight) 
				HighlightUpdate((char) e.KeyValue, userSelection.start-1);
			if (EditorConfiguration.doComment& reComment)
				ReComment();
#endif

			switch (e.KeyCode) {
				case Keys.Down:
				case Keys.Enter:
				case Keys.Up:
					CodeScanner cs = ScanCharsFrom(userSelection.start);
					if (cs.AtBeginningOfLine && cs.CurrentChar==Meta.TopLevel.promptCarret && userSelection.length==0)
						// Skip over prompt
						userSelection.start += 2;

					VerticalPositionChanged();
					break;
			}

			switch (e.KeyCode) {
					//case Keys.OemCloseBrackets:
					//	// Cursor is now just after bracket, so start at backet
					//	HighlightExpressionBefore(userSelection.start);
					//	break;

				case Keys.Enter:
					// Nuke leading whitespace
					if (!e.Control) {
						int whitespace = ScanCharsFrom(userSelection.start).SkipForwardWhiteSpaceToEOL() - userSelection.start;
						if (whitespace>0)
							SetRegionText(new Region(userSelection.start, whitespace), "");
					}
					break;

				case Keys.Up:
				case Keys.Down:
				case Keys.Left:
				case Keys.Right:
				case Keys.Home:
				case Keys.End:
				case Keys.T:
					if (e.Control && e.Alt) {
						CodeScanner cs = ScanCharsFrom(userSelection.start);
						switch (e.KeyCode) {
							case Keys.Up:
								cs.Backward();
								cs.SkipBackwardToEnclosingExpression();
								break;

							case Keys.Left:
								cs.SkipBackwardExpression();
								break;

							case Keys.Right:
								cs.SkipForwardExpression();
								break;

							case Keys.Home:
								cs.Backward();
								cs.SkipBackwardToTopLevelExpression();
								break;

							case Keys.End:
								cs.SkipBackwardToTopLevelExpression();
								cs.SkipForwardExpression();
								if (cs.charIndex==userSelection.Start)
									// We're back where we started, so go to the end of the next one
									cs.SkipForwardExpression();
								break;

							case Keys.T:
								HandleTranspose(null, null);
								break;
						}
						int boe = cs.charIndex;
						if (boe<userSelection.start) {
							// moved backward
							userSelection.length = 
								e.Shift?(userSelection.start+userSelection.length-boe):0;
							userSelection.start = boe;
						} else {
							// moved forward
							if (e.Shift)
								userSelection.End = boe;  // Extend selection right
							else {
								userSelection.length = 0;  // Just move the selection.
								userSelection.start = boe;
							}
						}
						VerticalPositionChanged();
						ScrollToCaret();
						e.Handled = true;
					} else if (e.Control) {
						switch (e.KeyCode) {
							case Keys.Up:
								PastePreviousCommand();
								break;

							case Keys.Down:
								PasteNextCommand();
								break;
						}
					} else if (e.KeyCode == Keys.Home) {
						if (RegionText(new Region(userSelection.start, TopLevel.promptString.Length))==TopLevel.promptString)
							userSelection = new Region(userSelection.start+TopLevel.promptString.Length, 0);
					}
					break;

				case Keys.Oemcomma:
					if (e.Control && e.Shift)
						FormatShrinkClick(null, null);
					break;
				case Keys.OemPeriod:
					if (e.Control && e.Shift)
						FormatGrowClick(null, null);
					break;

				case Keys.Oemplus:
					if (e.Control) {
						if (e.Shift)
							FormatSuperscriptClick(null, null);
						else
							FormatSubscriptClick(null, null);
					}
					break;
			}

			// Just for testing CurrentX properties
			//((Editor)FindForm()).SetStatusBarText(CurrentDefinitionVariable==null?"<null>":("'"+CurrentDefinitionVariable+"'"));

			if (e.KeyCode==Keys.Enter && e.Control  && EditorWindow is Listener) {
				// Sick kluge.  There's no good way to keep the RichTextBox from inserting a newline
				// when we type this.  That's okay for code buffers, but bad for the listener because the
				// eval code will print another newline.  So we delete the newline.  Eeeew...
				// This turns out to be the cause of the desktop redrawing/refreshing itself when
				// you do control-enter.  Don't know why.
				//SetRegionText(new Region(userSelection.start-1, 1), "");
				((Listener)EditorWindow).Out.SuppressNewLine();
			}

			RestoreUserSelection();
			MaybeHighlightExpression();
			UnlockWindowDammit();
			if (e.KeyCode==Keys.Enter && e.Control) {
				HandleEval(this, null);
			}
			return;
		}

		public void HandleSelectAll(object sender, EventArgs e) {
			SelectAll();
		}
		public void HandleCut(object sender, EventArgs e) {
			MarkUndo();
			Clipboard.SetDataObject(RegionText(userSelection));
			Cut();

#if SYNTAXHIGHLIGHTING
			if (EditorConfiguration.doComment)
			{
				NoteUserSelection();
				ReComment();
				RestoreUserSelection();
			}
#endif

		}
		public void HandleCopy(object sender, EventArgs e) {
			Unhighlight();
			Clipboard.SetDataObject(RegionText(userSelection));
			Copy();
		}
		public void HandlePaste(object sender, EventArgs e) {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			Paste();
			NoteUserSelection();
			IndentTopLevelExpression();

#if SYNTAXHIGHLIGHTING
			if (EditorConfiguration.doComment)
			{
				NoteUserSelection();
				ReComment();
				RestoreUserSelection();
			}
#endif
			RestoreUserSelection();
		}

		int pastedCommandIndex = -1;
		public Region pastedCommandRegion;
		public void ResetCommandPasting() {
			pastedCommandIndex=-1;
		}

		public static bool debuggingCommandPaste = true;
		public void PastePreviousCommand() {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			if (Listener.previousCommandLengths.Count==0)
				return; // Nothing to paste!

			if (pastedCommandIndex!=0) { // not on the last command
				if (pastedCommandIndex<0) {
					// Haven't started commands yet
					MarkUndo();
					pastedCommandIndex = Listener.previousCommands.Count-1;
					// KLUGE!  SelectionStart sometimes reads as 2 less that the actual location of the insertion point.
					//if (userSelection.start == TextLength-2 && userSelection.length == 0)
					//	userSelection.start += 2;

					//pastedCommandRegion = userSelection;

					// Sometimes, but not always, the richeditcontrol gets the control-up key and moves the cursor before we run.  Argh!
					pastedCommandRegion = new Region(TextLength, 0);
				} else
					// Go to the next command
					pastedCommandIndex--;

				int kluge = pastedCommandIndex;
				string newRTF = (string)Listener.previousCommands[pastedCommandIndex];
				if (pastedCommandRegion.End>TextLength)
					pastedCommandRegion.End = TextLength;
				SetRegionRTF(pastedCommandRegion, newRTF);  // this is very disturbing; according to dev studio, this line mutates pastedCommandIndex...
				kluge = Math.Min(kluge, Listener.previousCommandLengths.Count-1);
				pastedCommandIndex = kluge;
				pastedCommandRegion.length = (int)Listener.previousCommandLengths[kluge];
				if (pastedCommandRegion.End>TextLength)
					pastedCommandRegion.End = TextLength;
				userSelection.start=pastedCommandRegion.End;
				userSelection.length=0;
				IndentTopLevelExpression();
			}
			RestoreUserSelection();
		}

		public void PasteNextCommand() {
			Unhighlight();
			NoteUserSelection();

			if (pastedCommandIndex<Listener.previousCommands.Count-1) {
				pastedCommandIndex++;

				int kluge = pastedCommandIndex;
				string newRTF = (string)Listener.previousCommands[pastedCommandIndex];
				if (pastedCommandRegion.End>TextLength)
					pastedCommandRegion.End = TextLength;
				SetRegionRTF(pastedCommandRegion, newRTF);  // this is very disturbing; according to dev studio, this line mutates pastedCommandIndex...
				pastedCommandIndex = kluge;
				pastedCommandRegion.length = (int)Listener.previousCommandLengths[kluge];
				if (pastedCommandRegion.End>TextLength)
					pastedCommandRegion.End = TextLength;
				userSelection.start=pastedCommandRegion.End;
				userSelection.length=0;
				IndentTopLevelExpression();
			}
			RestoreUserSelection();
		}


		public void HandleTranspose(object sender, EventArgs e) {
			NoteUserSelection();
			MarkUndo();
			
			CodeScanner sc = ScanCharsFrom(userSelection.start);
			// Cursor is now either at the beginning of the right exp or in the whitespace between
			sc.SkipBackwardToWhiteSpace();
			// Cursor is now definitely in the whitespace
			sc.SkipBackwardWhiteSpace();
			// Cursor now on the last char of the left exp
			sc.Forward();
			// Cursor now on the first char of whitespace between
			int leftExpEnd = sc.charIndex;
			int leftExpStart = sc.SkipBackwardExpression();
			// Cursor now on the first char of left
			int leftExpLength = leftExpEnd-leftExpStart;

			sc.SkipForwardExpression();
			// Back to last char of left
			int rightExpStart = sc.SkipForwardWhiteSpace();
			// First char of right
			int rightExpEnd = sc.SkipForwardExpression();
			// Last char of right
			int rightExpLength = rightExpEnd-rightExpStart;

			Region left = new Region(leftExpStart, leftExpLength);
			Region right = new Region(rightExpStart, rightExpLength);
			string leftExpSaved = RegionRTF(left);

			SetRegionRTF(left, RegionRTF(right));
			int leftSizeChange = rightExpLength-leftExpLength;
			right.start += leftSizeChange;
			left.length = rightExpLength;
			SetRegionRTF(right, leftExpSaved);

			IndentTopLevelExpression();

			userSelection.start = left.End;
			userSelection.length = 0;
			RestoreUserSelection();
		}


		FontDialog fontDialog = new FontDialog();

		public void HandleTextChanged(object sender, EventArgs e) {
			if (!Disposing) {
				Modified = true;
				ModifiedSinceUndoMark = true;
				reIndent = true;
				reindentTimer.Stop();
				reindentTimer.Start();
			}
		}

		public void HandleFont(object sender, EventArgs e) {
			NoteUserSelection();
			MarkUndo();
			fontDialog.ShowColor = true;
			fontDialog.Font = SelectionFont;
			fontDialog.Color = SelectionColor;
			if(fontDialog.ShowDialog() != DialogResult.Cancel ) {
				SetRegionFont(userSelection, fontDialog.Font);
				SetRegionColor(userSelection, fontDialog.Color);
			}
		}

		public void HandleUndo(object sender, EventArgs e) {
			MyUndo();
		}

		public void HandleEval(object sender, EventArgs e) {
			HandleEvalInternal(false, false);
		}

		public void HandleStep(object sender, EventArgs e) {
			HandleEvalInternal(true, false);
		}

		public void HandleInspect(object sender, EventArgs e) {
			HandleEvalInternal(false, true);
		}

		private void HandleEvalInternal(bool stepMode, bool inspectMode) {
			LockWindow();
			ScrollPoint s = ScrollPosition;
			NoteUserSelection();
#if EvalRegions
			Region r=userSelection.length>0?userSelection:
				TopLevelExpressionRegion(userSelection.start);
#else
			userSelection.length=0;
			Region r = TopLevelExpressionRegion(userSelection.start);
#endif
			System.Diagnostics.Debug.Assert(!RegionText(r).StartsWith(" "));
			Listener.previousCommands.Add(RegionRTF(r));
			string text = RegionText(r);
			RestoreUserSelection();
			ScrollPosition = s;
			UnlockWindow();
			if (text.IndexOf(Meta.TopLevel.promptCarret)>=0) {
				System.Windows.Forms.MessageBox.Show("Please place the carret at the end of the expression and try again", "Can't find the expression", MessageBoxButtons.OK);
			} else {
				Listener.previousCommandLengths.Add(text.Length);
				Editor parent = EditorWindow;
				if (parent!=null && parent is Listener)
					((Listener)parent).EvalString(text+' ', stepMode, inspectMode, parent.fileToken, r.start, true);
				else
					Listener.mainListener.EvalString(text+' ', stepMode, inspectMode, parent.fileToken, r.start, true);
			}
		}

		public void HandleEvalFile(object sender, EventArgs e) {
			Meta.Reader reader = new Meta.Reader(new System.IO.StringReader(Text), 0);
			Listener.mainListener.LoadStream(reader, EditorWindow.fileToken);
		}

		public void HandleEvalRegion(object sender, EventArgs e) {
			NoteUserSelection();
			Meta.Reader reader = new Meta.Reader(new System.IO.StringReader(RegionText(userSelection)), userSelection.start);
			Listener.mainListener.LoadStream(reader, EditorWindow.fileToken);
		}

		public void HandleLoadFile(object sender, EventArgs e) {
			Stream myStream;
			OpenFileDialog openFileDialog1 = new OpenFileDialog();

			//openFileDialog1.InitialDirectory = "c:\\" ;
			openFileDialog1.Filter = "Source and object files (*.meta; *.dll)|*.meta;*.dll|.Net assemblies (*.dll)|*.dll|All files (*.*)|*.*" ;
			openFileDialog1.FilterIndex = 1 ;
			openFileDialog1.RestoreDirectory = true ;
			if (EditorWindow.fileToken.Path != null)
				openFileDialog1.InitialDirectory = Path.GetDirectoryName(EditorWindow.fileToken.Path);

			if(openFileDialog1.ShowDialog() == DialogResult.OK) {
				if (Path.GetExtension(openFileDialog1.FileName).ToLower()==".dll") {
					try {
						Loader.Load(openFileDialog1.FileName);
					} catch (Exception ex) {
						MessageBox.Show(ex.Message, "Error loading DLL", System.Windows.Forms.MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
				} else if((myStream = openFileDialog1.OpenFile())!= null) {
					// Insert code to read the stream here.
					Meta.Reader reader = new Reader(new RTFTextReader(new StreamReader(myStream)), 0);
					Listener.mainListener.LoadStream(reader, FileToken.GetToken(openFileDialog1.FileName));
				} else MessageBox.Show("Can't open the file "+openFileDialog1.FileName, "Error", System.Windows.Forms.MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		protected override void OnDragEnter(DragEventArgs drgevent) {
			if (drgevent.Data.GetDataPresent(DataFormats.FileDrop))
				drgevent.Effect = DragDropEffects.Copy;
			else
				base.OnDragEnter (drgevent);
		}

		protected override void OnDragDrop(DragEventArgs drgevent) {
			object[] data = (object[])drgevent.Data.GetData(DataFormats.FileDrop);
			string[] names = new string[data.Length];
			data.CopyTo(names,0);
			Listener.ProcessCommandLineArgs(names);
			//base.OnDragDrop (drgevent);
		}


		public void HandleStop(object sender, EventArgs e) {
			//			Listener.mainListener.SetAllCursors(Cursors.IBeam);
			if (EditorWindow is Listener)
				((Listener)EditorWindow).KillEvalThread();
			else
				Listener.mainListener.KillEvalThread();
		}

		public void FormatMenuPop(MenuItem formatBold, MenuItem formatItalic, MenuItem formatBullet) {
			Unhighlight();
			NoteUserSelection();
			System.Drawing.Font f = RegionFont(userSelection);

			if (f!=null) {
				formatBold.Checked = f.Bold;
				formatItalic.Checked = f.Italic;
			}
			formatBullet.Checked = RegionBullet(userSelection);
		}

		public void FormatBoldClick(object sender, EventArgs e) {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			System.Drawing.Font f = RegionFont(new Region(userSelection.start, 0));
			SetRegionFont(userSelection, new System.Drawing.Font(f, f.Style^FontStyle.Bold));
		}
		public void FormatItalicClick(object sender, EventArgs e) {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			System.Drawing.Font f = RegionFont(userSelection);
			SetRegionFont(userSelection, new System.Drawing.Font(f, f.Style^FontStyle.Italic));
		}
		public void FormatGrowClick(object sender, EventArgs e) {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			System.Drawing.Font f = RegionFont(userSelection);
			SetRegionFont(userSelection, new System.Drawing.Font(f.FontFamily, f.Size + 2.0F));
		}
		public void FormatShrinkClick(object sender, EventArgs e) {
			Unhighlight();
			NoteUserSelection();
			MarkUndo();
			System.Drawing.Font f = RegionFont(userSelection);
			SetRegionFont(userSelection, new System.Drawing.Font(f.FontFamily, f.Size - 2.0F));
		}
		public void FormatSuperscriptClick(object sender, EventArgs e) {
			Unhighlight();
			MarkUndo();
			NoteUserSelection();
			SetRegionCharOffset(userSelection, RegionCharOffset(userSelection)+2);
		}
		public void FormatSubscriptClick(object sender, EventArgs e) {
			Unhighlight();
			MarkUndo();
			NoteUserSelection();
			SetRegionCharOffset(userSelection, RegionCharOffset(userSelection)-2);
		}
		public void FormatBulletClick(object sender, EventArgs e) {
			Unhighlight();
			MarkUndo();
			NoteUserSelection();
			SetRegionBullet(userSelection, !RegionBullet(userSelection));
		}

		public void HandleStyle(object sender, System.EventArgs e) {
			string styleName = ((MenuItem)sender).Text.Replace("&", "");
			Style style = TheStyle(styleName);

			NoteUserSelection();
			MarkUndo();
			SetRegionStyle(userSelection, style);
			RestoreUserSelection();
		}

		public void ShowReferences(string name) {
			Listener.mainListener.EvalString("[Packages.ShowReferences.show-references '"+name+"]", false, false, EditorWindow.fileToken, 0, false);
		}

		public void HandleShowReferences(bool showDefinitionRefs) {
			string var = showDefinitionRefs?CurrentDefinitionVariable:CurrentVariableName;
			if (var!=null)
				ShowReferences(var);
		}

		//
		// Parsing of user's current selection
		//

		// Just the variable name part of the current token without any ".memberName"
		public string CurrentVariableName {
			get {
				return Utilities.StripMemberReferences(CurrentVariableNameOrMemberReference);
			}
		}

		// The full current token, including member references
		public string CurrentVariableNameOrMemberReference {
			get {
				// yuk
				if (userSelection.start==TextLength || (userSelection.start>=0 && userSelection.start<TextLength && char.IsWhiteSpace(Text[userSelection.start])))
					return RegionText(CurrentWord(userSelection.start-1));
				return RegionText(CurrentWord(userSelection.start));
			}
		}

		public Expression CurrentVariableExpression {
			get {
				Symbol s = Symbol.MaybeIntern(CurrentVariableName);
				if (s!=null)
					return this.Namespace.Lookup(s, true, false, true);
				else
					return null;
			}
		}

		public string CurrentExpressionOperator {
			get {
				// Find the enclosing expression
				CodeScanner cs = ScanCharsFrom(Math.Min(userSelection.start, TextLength));
				cs.SkipBackwardToEnclosingExpression();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Move forward over [
				cs.Forward();
				int operatorStart = cs.charIndex;
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Find the end of the variable name inside
				int operatorEnd = cs.SkipForwardToken();  // Find the end of this expression
				//if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
				//	return null;

				// Return the text
				return RegionText(new Region(operatorStart, operatorEnd-operatorStart));
			}
		}

		public string CurrentTopLevelExpression {
			get {
				// Find the start of the top-level expression
				CodeScanner cs = ScanCharsFrom(userSelection.start-1);
				cs.SkipBackwardToTopLevelExpression();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;
				int expStart = cs.charIndex;

				// Find the end of the expression
				int expEnd = cs.SkipForwardExpression();  // Find the end of this expression
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Return the text
				return RegionText(new Region(expStart, expEnd-expStart));
			}
		}	

		public string CurrentTopLevelExpressionOperator {
			get {
				// Find the start of the top-level expression
				CodeScanner cs = ScanCharsFrom(userSelection.start-1);
				cs.SkipBackwardToTopLevelExpression();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Move forward over [
				cs.Forward();
				int operatorStart = cs.charIndex;
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Find the end of the variable name inside
				int operatorEnd = cs.SkipForwardToken();  // Find the end of this expression
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Return the text
				return RegionText(new Region(operatorStart, operatorEnd-operatorStart));
			}
		}

		public int CurrentDefinitionStart {
			get {
				return StartOfDefinitionAt(userSelection.start-1);
			}
		}

		public int StartOfDefinitionAt(int index) {
			try {
				// Find the start of the top-level expression
				CodeScanner cs = ScanCharsFrom(index);
				cs.SkipBackwardToTopLevelExpression();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return -1;
				else return cs.charIndex;
			} catch {
				return -1;
			}
		}

		public string CurrentDefinitionVariable {
			get {
				return DefinitionNameAt(userSelection.start-1);
			}
		}

		public string DefinitionNameAt(int index) {
			try {
				// Find the start of the top-level expression
				CodeScanner cs = ScanCharsFrom(index);
				cs.SkipBackwardToTopLevelExpression();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Move forward over [
				cs.Forward();
				int operatorStart = cs.charIndex;
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Find the end of the variable name inside
				int operatorEnd = cs.SkipForwardToken();  // Find the end of this expression
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Check it's a definition
				if (RegionText(new Region(operatorStart, Math.Min(3,operatorEnd-operatorStart))) != "def")
					return null;

				// Find the variable name
				cs.SkipForwardWhiteSpace();
				while (char.GetUnicodeCategory(cs.CurrentChar)==UnicodeCategory.OpenPunctuation)
					cs.Forward();
				// Here we are
				int varStart = cs.charIndex;
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				int varEnd = cs.SkipForwardToken();
				if (cs.AtBeginningOfBuffer | cs.AtEndOfBuffer)
					return null;

				// Return the text
				return RegionText(new Region(varStart, varEnd-varStart));
			} catch (Exception e) {
				e=e;
				return null;
			}

		}
		//
		// CONTEXT SENSITIVE INPUT PROCESSING
		//

		private bool IsTypingAfter(string text) {
			return text==RegionText(new Region(userSelection.start-text.Length, text.Length));
		}

		private bool MaybeChangeChar(string oldText, string newText) {
			Region charsBefore
				= new Region(userSelection.start-oldText.Length,oldText.Length);
			if (RegionText(charsBefore)==oldText) {
				char theChar = newText[0];
				object matching = CodeScanner.matchingBracket[theChar];

				if (Editor.insertMatchingBrackets && newText.Length==1
					&& (char.GetUnicodeCategory(theChar)==UnicodeCategory.OpenPunctuation ||
					char.GetUnicodeCategory(theChar)==UnicodeCategory.InitialQuotePunctuation)
					&& matching != null)
					SetRegionText(charsBefore, newText+new String((char)matching, 1));
				else
					SetRegionText(charsBefore, newText);
				return true;
			}
			return false;
		}
		private bool MaybeInsertOperator(string newText) {
			string TextBefore = RegionText(new Region(userSelection.start-1,1));
			if (TextBefore==" " || TextBefore=="[") {
				SetRegionText(userSelection, newText);
				return true;
			}
			return false;
		}

		//
		// SCANNING OPERATIONS
		//

		public CodeScanner ScanCharsFrom(int charIndex) {
			return new CodeScanner(Math.Min(charIndex, TextLength), Lines);
		}

		public Region ExpressionRegionBefore(int pos) {
			CodeScanner sc = ScanCharsFrom(pos);
			int start=sc.SkipBackwardExpression(false);
			if (sc.AtBeginningOfBuffer)
				return new Region(pos, 0);
			else
				return new Region(start, pos-start);
		}

		public Region ExpressionRegionAfter(int pos) {
			CodeScanner sc = ScanCharsFrom(pos);
			int end=sc.SkipForwardExpression();
			if (sc.AtEndOfBuffer)
				return new Region(pos, 0);
			else
				return new Region(pos, end-pos);
		}

		public Region TopLevelExpressionRegion(int pos) {
			CodeScanner s=ScanCharsFrom(pos);
			int start=s.SkipBackwardToTopLevelExpression();
			int end=s.SkipForwardExpression();

			return new Region(start, end-start);
		}

		//want Lines to just be current line
		Region CurrentWord(int position) {
			CodeScanner GetWord = ScanCharsFrom(position);
			int start = CurrentWordStart(GetWord);
			CodeScanner GetWord2 = ScanCharsFrom(position);
			int end = CurrentWordEnd(GetWord2);
			if (start < end) 
				return new Region(start, (end-start)+1);
			else
				return new Region(0,0);
		}

		//i really only need the current line for this - unless the delims change
		//to where newline isn't necessarily a delim
		int CurrentWordStart(CodeScanner cs) {
			int start;
			if (cs.AtEndOfBuffer || Char.GetUnicodeCategory(cs.CurrentChar)==UnicodeCategory.ClosePunctuation)
				cs.Backward();
			if ((start = cs.SkipBackwardToWordSeparator()) < 0)
				return 0;
			else
				return ++start;
		}

		//i really only need the current line for this
		//need to rewrite this with better error handling
		int CurrentWordEnd(CodeScanner cs) {
			int end;
			if ((end = cs.SkipForwardToWordSeparator()) < 0)
				end = TextLength+1;
			return --end;
		}

		//get's the word to the left of the current delim position
		Region WordToLeft(int position) {
			int start;
			//if the delim is at the first char in the buffer, there can't be
			//any words to it's left
			if (position == 0)
				start = 0;
			else {
				CodeScanner GetLeftWord = ScanCharsFrom(position-1);
				start = CurrentWordStart(GetLeftWord);
			}
			return new Region(start, position-start);
		}

		//get's the word to the right of the current delim position
		Region WordToRight(int position) {
			//if the delim is the last char in the window, it can't have any words
			//to it's right
			if (position == TextLength-1)
				return new Region(0, 0);
			else {
				CodeScanner GetRightWord = ScanCharsFrom(position+1);
				return new Region(position+1, CurrentWordEnd(GetRightWord)-(position));
			}
		}

		//
		// INDENTATION
		//

#if OLDREINDENT
		private void ReindentCurrentLine() {
			IndentLine(userSelection.start);
		}

		public void IndentLine(int pos) {
			// Basic idea:
			// Skip backward to the second subexpression of this expression,
			// get its position and indent the current selection to that position.

			int indentPoint=0;    // buffer index of the beginning of the second expression
			int firstSubStart;      // char character and line number of the first subexpression of the enclosing expression.
			int firstSubLineNo;

			CodeScanner s = ScanCharsFrom(pos);
			s.SkipBackwardToBOL();
			if (s.CurrentChar!=TopLevel.promptCarret) {
				if (s.MaybeSkipBackwardToEnclosingExpression() && Char.GetUnicodeCategory(s.CurrentChar)==UnicodeCategory.OpenPunctuation) {
					// Skip over the [
					s.Forward();
					// Check what kind of expression it is
					object indentStyle = null;
					if (Editor.indentDefsLispStyle && RegionText(new Region(s.charIndex, 3))=="def")
						indentStyle = Symbol.sBody;
					else {
						string t = Text;  // Cheat to quickly find symbol name
						int symEnd = t.IndexOf(' ',s.charIndex);

						if (symEnd>0) {
							string symName = t.Substring(s.charIndex, symEnd-s.charIndex);
							Meta.Parser maybeParser = Meta.Namespace.CurrentNamespace.CheckBinding(symName) as Meta.Parser;

							if (maybeParser!= null)
								indentStyle = maybeParser.IndentStyle;
						}
					}

					if (indentStyle==Symbol.sBody) {
						// It ends in a body; Indent two chars in.
						indentPoint = s.charIndex+2;
					} else if (indentStyle==Symbol.sWith) {
						int withStart = s.charIndex;
						// Skip over with
						s.SkipForwardExpression();
						s.SkipForwardWhiteSpace();
						indentPoint = s.charIndex;  // Assume we'll be intendint to the level of the second sexp
						// Now skip over the = clauses
						while (s.charIndex < pos) {
							// Skip the variable name
							s.SkipForwardExpression();
							// Is there an = here?
							s.SkipForwardWhiteSpace();
							if (s.CurrentChar=='=') {
								// Skip it
								s.SkipForwardExpression();
								// And now skip the value
								s.SkipForwardWhiteSpace();
								s.SkipForwardExpression();
							} else if (s.CurrentChar==':') {
								// Oops, this is a named let.  The next expresion is actually the indent point.
								s.SkipForwardExpression();
								s.SkipForwardWhiteSpace();
								indentPoint = s.charIndex;
							} else {
								// Okay, the code we're trying to indent comes *after* the end of the = clauses
								// so we *don't* indent it to the level of the second sexp; instead, we indent to
								// the 't' in "with".
								indentPoint = withStart+2;
								break;
							}
						}
					} else if (char.GetUnicodeCategory(s.CurrentChar)==UnicodeCategory.OpenPunctuation) {
						indentPoint = s.charIndex;
					} else {
						// Find the beginning of the second subexpression of the enclosing expression.
						firstSubStart = s.charIndex;
						firstSubLineNo = s.lineNumber;
						s.SkipForwardExpression();
						s.SkipForwardWhiteSpace();
						if (s.lineNumber == firstSubLineNo) {
							if (s.CurrentChar == ':')
								// It's a function definition, indent the body two chars in from the head.
								indentPoint = firstSubStart+2;
							else {
								// The second subexpression is on the same line as the first, so we indent to it
								indentPoint = s.charIndex;
								// But first check to make sure this isn't the arglist of a function...
								while (!s.AtEndOfBuffer && s.lineNumber == firstSubLineNo) {
									char c = s.CurrentChar;
									if (c == ']')
										// Oops, we're confused.
										break;
									if (c == '\u2192') {
										// Oops, this is a function definition; indent to to head plus two chars.
										indentPoint = firstSubStart+2;
										break;
									}
									s.SkipForwardExpression();
									s.SkipForwardWhiteSpace();
								}
								// We got to the next line safely, so it must not be a function definition.
								// So leave the indent point at the second subexpression.
							}
						} else
							// There's only one subexpression on this line, so indent to it.
							indentPoint = firstSubStart;
					}
				}
			}
			// else punt as use 0 for the indentation...

			// The only way to get the current indent of a position is to ask for its screen location
			int screenPosition = GetPositionFromCharIndex(indentPoint).X;
			// But then you have to subtract off the indent of something "unindented", especially
			// since the screen position changes when you scroll horizontally.
			int scrollPosition = GetPositionFromCharIndex(0).X;

			SetRegionIndent(new Region(pos,0),
				indentPoint>0?(screenPosition-scrollPosition):0);
		}
#endif

		void ReindentHandler(object sender, EventArgs e) {
			NoteUserSelection();
			IndentTopLevelExpression();
			RestoreUserSelection();
		}

		public void FinishReindentBeforeSave() {
			if (reindentTimer.Enabled) {
				reindentTimer.Stop();
				ReindentHandler(null, null);
			}
			MarkUndo();
		}

		public void IndentTopLevelExpression() {
			if (reIndent) {
#if PROFILEINDENT
			long startTicks = System.Diagnostics.Process.GetCurrentProcess().UserProcessorTime.Ticks;
#endif
				LockWindow();
				ScrollPoint savedScroll = ScrollPosition;
				Region top = TopLevelExpressionRegion(userSelection.Start);
				if (!currentTopLevelRegion.Equals(top) || reIndent) {
					currentTopLevelRegion = top;
					SetRegionIndent(new Region(top.start, 0), 0);  // Force the start of the TLE to the left margin
#if OLDREINDENT
				CodeScanner cs = ScanCharsFrom(top.start);
			
				do {
					IndentLine(cs.charIndex);
					cs.SkipForwardToBOL();
				} while (cs.charIndex<top.End && !cs.AtEndOfBuffer && !cs.IsBlankLine && !cs.IsDefinitionLine);
				IndentLine(userSelection.Start);  // just in case
#else
					//				new Indenter(this, top.start).Read();     // for debugging, so we can see what error is dropping it out.
					if (top.length>1)
						Formatter.FormatExpression(this, top.start, false, false, false);
#endif
				}
				RestoreUserSelection();  // Otherwise the scroll position will just immediately set itself back :-(
				ScrollPosition = savedScroll;
				UnlockWindow();
				reIndent = false;
#if PROFILEINDENT
			long elapsedTicks = System.Diagnostics.Process.GetCurrentProcess().UserProcessorTime.Ticks-startTicks;
			double time = ((double)elapsedTicks)/System.TimeSpan.TicksPerSecond;
			((Editor)FindForm()).SetArglistText(time.ToString()+" sec");
#endif
			}
			reindentTimer.Stop();
		}

		public void RemoveLeadingWhitespace() {
			int position=0;
			int line=0;
			bool firsttime=true;

			Editor e = EditorWindow;
			NoteUserSelection();

			while (true) {
				e.SetArglistText("Line "+line.ToString());
				line++;
				if (position>=TextLength) {
					userSelection=new Region(0,0);
					RestoreUserSelection();
					return;
				}
				CodeScanner s=ScanCharsFrom(position);
				if (!firsttime) {
					s.SkipForwardToBOL();
					position = s.charIndex;
				} else
					firsttime = false;
				if (char.IsWhiteSpace(s.CurrentChar)) {
					int whitespace = s.SkipForwardWhiteSpaceToEOL() - position;
					if (whitespace>0)
						SetRegionText(new Region(position, whitespace), "");
				}
			}
		}

		public void PrettyPrintKluge() {
			CodeScanner sc = ScanCharsFrom(TextLength);
			sc.SkipBackwardExpression(); // Skip over the Carret.
			sc.SkipBackwardExpression(); // Move to the start of the expression we want to post-pretty-print
			PrettyPrintExp(sc, GetPositionFromCharIndex(0).X+Width-20);
			SetRegion(new Region(TextLength, 0));
		}

		void PrettyPrintExp(CodeScanner sc, int bufferRight) {
			// First check if there's anything to do
			if (sc.CurrentChar != '[')
				// It's atomic
				return;
			CodeScanner cpy = new CodeScanner(sc);
			cpy.SkipForwardExpression();
			if (cpy.lineNumber == sc.lineNumber && GetPositionFromCharIndex(cpy.charIndex).X < bufferRight)
				// Not atomic, but fits on the line
				return;

			// Not atomic and doesn't fit on the line.

			int subexpressionIndex=0;
			sc.Forward();   // Skip the [
			sc.SkipForwardWhiteSpace();
			while (!sc.AtEndOfBuffer && sc.CurrentChar!=']') {
				sc.SkipForwardWhiteSpace();
				// Look ahead to see if it will fit on this line.
				CodeScanner sub = new CodeScanner(sc);
				sub.SkipForwardExpression();
				if (GetPositionFromCharIndex(sub.charIndex).X>=bufferRight) {
					// It doesn't fit.  Start a new line for it.
					if (subexpressionIndex>0)
						// We can only assume there's a space char here to turn into a cr
						// if it isn't the first subexpression.  Fortunately, we won't want
						// to break the first SE into a separate line anyway.
						SetRegionText(new Region(sc.charIndex-1, 1), "\n");
#if OLDINDENTER
					IndentLine(Math.Min(TextLength,sc.charIndex));
#endif

					// Check if it still overflows
					if (GetPositionFromCharIndex(sub.charIndex).X>=bufferRight)
						// Break it up, if possible
						PrettyPrintExp(new CodeScanner(sc), bufferRight);
					// Next subexpression
				}
				sc = sub;
				subexpressionIndex++;
			}
		}


		//
		// REGION OPERATIONS
		// The underlying RichTextBox only supports a single selection that you
		// access by setting SelectionStart, etc.  Regions let you pretend you
		// can have multiple simultaneous selections.  They also let you treat
		// selections as first-class data objects
		//

		public Region userSelection;
		public void NoteUserSelection() {
#if HIGHLIGHTBYCOLORING
			userSelection.start = SelectionStart;
			userSelection.length = SelectionLength;
			currentRegion = userSelection;
#else
			if (highlightedRegion.length==0 ||  // nothing highlighted
				SelectionStart !=highlightedRegion.start || SelectionLength != highlightedRegion.length  // cursor got moved from highlighting
				) {
				userSelection.start = SelectionStart;
				userSelection.length = SelectionLength;
				currentRegion = userSelection;
			}
#endif
		}
		public void RestoreUserSelection() {
			SetRegion(userSelection);
		}

		private Region highlightedRegion;
#if HIGHLIGHTBYCOLORING
		private string highlightedRegionOldRTF = null;
#endif

		public void Unhighlight(object sender, EventArgs ignore) {
			LockWindow();
			ScrollPoint s = ScrollPosition;
			Unhighlight();
			// Have to do this because RestoreUserSelection sets Start before Length,
			// so the region can run of the end of the screen, forcing a scroll.
			SelectionLength = 0;
			RestoreUserSelection();
			ScrollPosition = s;
			UnlockWindow();
		}

		bool IsHighlighted {
			get {
				return highlightedRegion.length>0;
			}
		}

		public void Unhighlight() {
#if HIGHLIGHTBYCOLORING
			if (highlightedRegion.length>0) {
				//SetRegionColor(highlightedRegion, ForeColor);
				SetRegion(highlightedRegion);
				SelectedRtf = highlightedRegionOldRTF;
				highlightedRegion.length=0;
			}
#else
			if (IsHighlighted)
				unhighlightTimer.Stop();
			highlightedRegion.length=0;
#endif
		}
		public void HighlightRegion(Region r, Color c, bool autoClear) {
#if HIGHLIGHTBYCOLORING
			if (r.start!=highlightedRegion.start || r.length!=highlightedRegion.length)
				Unhighlight();
			highlightedRegion = r;
			highlightedRegionOldRTF = RegionRTF(r);
			SetRegionColor(r, c);
#else
			LockWindow();
			ScrollPoint s = ScrollPosition;
			highlightedRegion = r;
			SetRegion(r);
			if (autoClear)
				unhighlightTimer.Start();
			ScrollPosition = s;
			UnlockWindow();
#endif
		}

		// Highlight the expression before the selection.
		public void MaybeHighlightExpression() {
			if (!IsHighlighted) {
				if (userSelection.length==0 && userSelection.start>0 && userSelection.start<=Text.Length) {
					char charBefore = Text[userSelection.start-1];
					UnicodeCategory catBefore = char.GetUnicodeCategory(charBefore);
					if (catBefore==UnicodeCategory.ClosePunctuation || catBefore==UnicodeCategory.FinalQuotePunctuation
						&& charBefore != endOfCommentChar) {
						HighlightExpressionBefore(userSelection.start);
					}
				}
			}
		}

		// Highlight the expression starting at the selection
		public void MaybeHighlightAfter() {
			if (userSelection.length==0 && userSelection.start>0 && userSelection.start<=Text.Length-1) {
				char charBefore = Text[userSelection.start];
				UnicodeCategory catBefore = char.GetUnicodeCategory(charBefore);
				if (catBefore==UnicodeCategory.OpenPunctuation || catBefore==UnicodeCategory.InitialQuotePunctuation
					&& charBefore != startOfCommentChar) {
					HighlightExpressionAfter(userSelection.start);
				}
			}
		}

		public string RegionText(Region r) {
			if (r.length == 0) return ""; //null;
			else {
				//SetRegion(r);
				//return SelectedText;
				// This is *probably* faster and it doesn't require updating the selection,
				// which can cause unwanted blinking.
				
				string t=Text;
				if (r.start+r.length>t.Length)
					return t.Substring(r.start);
				else
					return t.Substring(r.start, r.length);
			}
		}
		public void SetRegionText(Region r, string newText) {
			Unhighlight();
			SetRegion(r);

			SelectedText = newText;
		}

		public string RegionRTF(Region r) {
			if (r.length == 0) return ""; //null;
			else {
				SetRegion(r);
				return SelectedRtf;
			}
		}
		public void SetRegionRTF(Region r, string newRTFText) {
			Unhighlight();
			SetRegion(r);

			SelectedRtf = newRTFText;
		}

		public Color RegionColor(Region r) {
			SetRegion(r);
			return SelectionColor;
		}
		public void SetRegionColor(Region r, Color c) {
			SetRegion(r);
			SelectionColor = c;
		}

		public Font RegionFont(Region r) {
			SetRegion(r);
			return SelectionFont;
		}
		public void SetRegionFont(Region r, Font f) {
			SetRegion(r);
			SelectionFont = f;
		}

		public Style RegionStyle(Region r) {
			return new Style(RegionFont(r), RegionColor(r));
		}

		public void SetRegionStyle(Region r, Style s) {
			SetRegionFont(r, s.font);
			SetRegionColor(r, s.color);
		}

		public int RegionCharOffset(Region r) {
			return SelectionCharOffset;
		}
		public void SetRegionCharOffset(Region r, int offset) {
			SetRegion(r);
			SelectionCharOffset = offset;
		}

		public int RegionIndent(Region r) {
			SetRegion(r);
			return SelectionIndent;
		}
		public void SetRegionIndent(Region r, int pixels) {
			SetRegion(r);
			if (SelectionIndent != pixels)
				SelectionIndent = pixels;
		}

		public bool RegionBullet(Region r) {
			SetRegion(r);
			return SelectionBullet;
		}

		public void SetRegionBullet(Region r, bool state) {
			SetRegion(r);
			SelectionBullet = state;
		}

		Region currentRegion;

		void SetRegion(Region r) {
			if (r.start != currentRegion.start || r.length != currentRegion.length) {
				SelectionLength=0;   // Keep it from accidentally running off the screen or buffer when changing start.
				SelectionStart = Math.Max(0, r.start);
				SelectionLength = Math.Max(0, r.length);
				currentRegion = r;
			}
		}

		public void ScrollToPosition(int position) {
			SetRegion(new Region(TextLength-1, 0));
			userSelection = new Region(position, 0);
			RestoreUserSelection();
		}

		//
		// Incrementally update whatever automatic formatting needs to be updated
		// after a key is typed.
		//
		protected void UpdateTextStyles(char keyChar) {
			switch (keyChar) {
				case ' ':
				case '\n':
				case '\r':
				case ']':
				case '[':
				case '(':
				case ')':
					if (keyChar=='(')
						keyChar = '[';
					CodeScanner sc = ScanCharsFrom(userSelection.start);
					// Find the start of the line, skipping any prompt
					sc.SkipBackwardToBOL();
					if (sc.CurrentChar==TopLevel.promptCarret) {
						sc.Forward();
						sc.SkipForwardWhiteSpace();
					}

					// Check if the line starts with "[def"
					if (!sc.LookingAt("[def") && !sc.LookingAt("(def"))
						// Not a definition
						break;
					// It's a definition; check to see if we're in title position.
					sc.Forward();									// Skip the bracket.
					sc.SkipForwardToken();					// Skip over def-whatever
					while (sc.charIndex<userSelection.start && (Char.IsWhiteSpace(sc.CurrentChar) || sc.CurrentChar=='[' || sc.CurrentChar =='('))
						sc.Forward();

					bool atTitle = userSelection.Start<=sc.charIndex;
					if (atTitle && keyChar=='[')
						nextStyle = TheStyle("Title");
					SetRegionStyle(userSelection, (keyChar!='[' && atTitle)?TheStyle("Title"):TheStyle("Normal"));
					// In theory, this should do it.

					//					SetRegionCharOffset(userSelection, 0);
					//					int endExp = userSelection.Start;
					//					CodeScanner sc = ScanCharsFrom(endExp);
					//					int startExp = sc.SkipBackwardExpression();
					//					if ((endExp-startExp) < 30) {
					//						if (RegionText(new Region(startExp, endExp-startExp)).StartsWith("define")) {
					//							if (keyChar=='[') {
					//								SetRegionStyle(userSelection, TheStyle("Normal"));
					//								nextStyle = TheStyle("Title");
					//							} else
					//								SetRegionStyle(userSelection, TheStyle("Title"));
					//						} else {
					//							endExp = startExp;
					//							startExp = sc.SkipBackwardExpression();
					//							if ((endExp-startExp) < 30 && RegionText(new Region(startExp, endExp-startExp)).StartsWith("define"))
					//								SetRegionStyle(userSelection, TheStyle("Normal"));
					//						}
					//						MarkUndo();
					//					}
					break;

				case '<':
					if (IsTypingAfter(startOfCommentString))
						SetRegionStyle(userSelection, TheStyle("Comment"));
					break;

				case ';':
					SetRegionStyle(userSelection, TheStyle("Comment"));
					break;

				case '>':
					if (IsTypingAfter(endOfCommentString))
						SetRegionStyle(new Region(userSelection.Start-1, userSelection.End-(userSelection.Start-1)), TheStyle("Normal"));
					break;

#if SYNTAXHIGHLIGHTING		
				case '<':
					if (EditorConfiguration.doComment & IsTypingAfter(startOfCommentString)) {
						int closeComment=ScanCharsFrom(userSelection.start).SkipForwardToChar(endOfCommentChar);
						if (closeComment==-1) {
							closeComment=TextLength;
						}	
						SetRegionColor(new Region(userSelection.start-1, closeComment-userSelection.start+2),
							EditorConfiguration.commentColor);
					}
					break;

				case '>':
					if (EditorConfiguration.doComment & IsTypingAfter(endOfCommentString)) {
						int nextComment=ScanCharsFrom(userSelection.start).SkipForwardToChar(startOfCommentChar);
						if (nextComment==-1) {
							nextComment=TextLength;
						}
						SetRegionColor(new Region(userSelection.start, nextComment-userSelection.start), this.ForeColor);
					}
					break;
#endif
			}
		}


		/// <summary>
		/// Paren-Matching Highlighting
		/// </summary>
		/// <param name="pos"></param>
		/// 

		void HighlightExpressionBefore(int pos) {
			HighlightRegion(ExpressionRegionBefore(pos), Color.LawnGreen, true);
		}

		void HighlightExpressionAfter(int pos) {
			HighlightRegion(ExpressionRegionAfter(pos), Color.LawnGreen, false);
		}

#if SYNTAXHIGHLIGHTING
		/// <summary>
		/// Syntax highlighting
		/// </summary>
		/// <param name="KeyWords"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		//returns nomatch if the hashtable doesn't contain the keyword, 
		//returns the color associated with the keyword otherwise
		private string MatchKeyWord(Hashtable KeyWords, string key)
		{
			if (key != null && KeyWords.ContainsKey(key)) 
				return (string)KeyWords[key];
			else 
				return null;
		}

		//update all the highlighting
		//if keypress was a delim, check before and after it
		//if it wasn't a delim, check the current word

		private void HighlightUpdate(char keypress, int position) {
			if (!char.IsWhiteSpace(keypress) && !char.IsSeparator(keypress)) {
				SyntaxHighlightKeyword(CurrentWord(position));
			} else {
				SyntaxHighlightKeyword(WordToLeft(position));
				SyntaxHighlightKeyword(WordToRight(position));
			}
		}

		//syntaxhighlight the entire codebox
		public bool SyntaxHighlight() {
			NoteUserSelection();
			bool success = SyntaxHighlight(0, Lines);
			RestoreUserSelection();
			return success;
		}
				
		//syntax highlight a portion of the codebox
		//i have to admit, i have not tested this on any input but lines=Lines[]
		public bool SyntaxHighlight(int start, string[] lines) 
		{
			bool success=false;
			if (EditorConfiguration.canHighlight){ 
				int WordStartsAt = start, WordEndsBefore;
				CodeScanner cs = ScanCharsFrom(start);
				while(!cs.AtEndOfBuffer && 
					((WordEndsBefore = cs.SkipForwardToWordSeparator()) >= (int) Error.SUCCESSFUL)) {
					SyntaxHighlightKeyword(new Region(WordStartsAt, WordEndsBefore-WordStartsAt));
					cs.Forward();
					WordStartsAt = cs.charIndex;
				}
				success = true;
			}
			return success;
		}

		public bool SyntaxHighlight(int start, int end, string[] lines) {
			bool success=false;
			if (EditorConfiguration.canHighlight){ 
				int WordStartsAt = start, WordEndsBefore;
				CodeScanner cs = ScanCharsFrom(start);
				while(!cs.AtEndOfBuffer && (cs.charIndex < end) &&
					((WordEndsBefore = cs.SkipForwardToWordSeparator()) >= (int) Error.SUCCESSFUL)) {
					SyntaxHighlightKeyword(new Region(WordStartsAt, WordEndsBefore-WordStartsAt));
					cs.Forward();
					WordStartsAt = cs.charIndex;
				}
				success = true;
			}
			return success;
		}

		//syntax highlight a legitimate keyword
		int SyntaxHighlightKeyword(Region Word) {
			int status = -1;
			if (this.CommentInRegion(Word)|| this.InCommentedRegion(Word.start))
				return status;
			if (Word.length > 0) {
				string WordColor;
				if ((WordColor = MatchKeyWord(EditorConfiguration.keywords, RegionText(Word))) != null)  {
					//SetRegionColor(Word, Colors[WordColor]);
					SetRegionColor(Word, (Color)colorConverter.ConvertFromString(WordColor));
					status = 0;
				}
				else 
					SetRegionColor(Word, ForeColor);
			}
			return status;
		}

		public void SyntaxUnhighlight() {
			SetRegionColor(new Region(0, TextLength), ForeColor);
		}

		//Comment Highlight Everything
		int HighlightComments() {
			//HighlightComments();
			return 0;
		}

		//Comment Highlight a Phrase
		int HighlightComments(int start, int end) {
			return 0;
		}

		//rachel, jeff, mazen added comment stuff
		
		public void CommentRegion(Region r)
		{
			CodeScanner cs;
			int endcomment=r.start;
			int end=r.start+r.length;
			while (endcomment<end)
			{
				cs=ScanCharsFrom(endcomment);
				int startcomment=cs.SkipForwardToChar(startOfCommentChar);
				if (startcomment==-1)//if we cant find a comment set the region black and quit
				{
					SetRegionColor(new Region(endcomment, r.length), this.ForeColor);
					SyntaxHighlight(endcomment, end, Lines);
					endcomment=end;
					break;
				}
				//gotta change the whole thing to forecolor first then syntax highlight, those are just the rules
				SetRegionColor(new Region(endcomment, startcomment-endcomment),
					this.ForeColor);
				SyntaxHighlight(endcomment, startcomment+1, Lines);
				

				int closingbracket=cs.SkipForwardToChar(endOfCommentChar);
				Region c;
				c.start=startcomment;
				if (closingbracket!=-1)
					endcomment=closingbracket;
				else
					endcomment=end;
				c.length=endcomment-startcomment+1; 
				SetRegionColor(c, EditorConfiguration.commentColor);//this color is now an option
				endcomment++;
			}
		}

		public void ReComment()
		{
			CodeScanner cs=ScanCharsFrom(userSelection.start);
			int prevComment=cs.SkipBackwardToToken(startOfCommentChar); //find last start of comment
			if (prevComment==-1)
				prevComment=0;		//if we can't find that, start from the beginning
			cs=ScanCharsFrom(userSelection.start);
			int nextComment=cs.SkipForwardToChar(startOfCommentChar); //find next comment and call comment region
			if (nextComment==-1)
				nextComment=TextLength;
			Meta.Editor.Region commentMe;
			commentMe.start=prevComment;
			commentMe.length=nextComment-prevComment+1;
			CommentRegion(commentMe);
			reComment=false;
		}

		public bool InCommentedRegion(int start)
		{
			CodeScanner cs=ScanCharsFrom(start);
			int openbrak=cs.SkipBackwardToToken(startOfCommentChar);
			cs=ScanCharsFrom(start);
			int closebrak=cs.SkipBackwardToToken(endOfCommentChar);
			return openbrak > closebrak; //if open brak is closer then you're in a comment
		}

		public bool CommentInRegion(Region Word)
		{
			int start = Word.start;
			int length = Word.length;
			string mystr = RegionText(new Region(start, length+1));
			foreach (char c in mystr.ToCharArray(0, mystr.Length))
			{
				if (c.Equals(endOfCommentChar) || c.Equals(startOfCommentChar))
					return true;
			}
			return false;
		}

		public void ReCommentAll(){
			NoteUserSelection();
			CommentRegion(new Region(0, this.TextLength));
			RestoreUserSelection();
		}
#endif
	
		/// <summary>
		/// Crappy undo support
		/// This is *so* *so* ugly.
		/// 
		public void MarkUndo() {
			Unhighlight();
			if (ModifiedSinceUndoMark && undoEnabled) {
				if (undoStack.Count>0) {
					UndoRecord top = (UndoRecord)undoStack.Peek();
					top.scrollPosition = ScrollPosition;
					top.selection = userSelection;
				}
				string kluge = Rtf;

				if (kluge.Length>50000)
					undoEnabled = false;
				else
					undoStack.Push(new UndoRecord(userSelection, ScrollPosition, Rtf));  // save the whole *ing buffer as an RTF string...
			}
			ModifiedSinceUndoMark = false;
		}

		public void MyUndo() {
			if (undoStack.Count>0) {
				UndoRecord lastMod = (UndoRecord)undoStack.Peek();
				if (undoStack.Count>1)
					undoStack.Pop();

				userSelection = lastMod.selection;
				LockWindow();
				Rtf = lastMod.bufferRTF;     // restore the whole *ing buffer.
				RestoreUserSelection();
				ScrollPosition = lastMod.scrollPosition;
				UnlockWindow();
			}
			ModifiedSinceUndoMark = false;
		}

		public class UndoRecord {
			public Region selection;
			public string bufferRTF;  // kluge!
			public ScrollPoint scrollPosition;
			public UndoRecord(Region selection, ScrollPoint scrollPosition, string bufferRTF) {
				this.selection = selection;
				this.scrollPosition = scrollPosition;
				this.bufferRTF = bufferRTF;
			}
		}

		//const int SB_VERT = 1; 
		const int EM_GETSCROLLPOS = 0x0400 + 221; 
		const int EM_SETSCROLLPOS = 0x0400 + 222; 
  
		//[DllImport("user32", CharSet=CharSet.Auto)] 
		//public static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos); 
 
		[DllImport("user32", CharSet=CharSet.Auto)] 
		public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ScrollPoint lParam); 
 
		[StructLayout(LayoutKind.Sequential)] 
			public class ScrollPoint { 
			public int x; 
			public int y; 
 
			public ScrollPoint() { 
			} 
 
			public ScrollPoint(int x, int y) { 
				this.x = x; 
				this.y = y; 
			} 
		} 

		public ScrollPoint ScrollPosition {
			set {
				SendMessage(Handle, EM_SETSCROLLPOS, 0, value);
			}
			get {
				ScrollPoint p = new ScrollPoint();
				SendMessage(Handle, EM_GETSCROLLPOS, 0, p);
				return p;
			}
		}

		public int TopOfScreenScrollTarget = 80;
		public void ScrollToTop() {
			ScrollToTop(userSelection.start);
		}
		public void ScrollToTop(int index) {
			ScrollPoint s = ScrollPosition;
			Point currentPosition = GetPositionFromCharIndex(index);
			s.y -= TopOfScreenScrollTarget-currentPosition.Y;
			if (s.y>=0)
				ScrollPosition = s;
		}

		public void ScrollToCenter() {
			ScrollToCenter(userSelection.start);
		}
		public void ScrollToCenter(int index) {
			ScrollPoint s = ScrollPosition;
			Point currentPosition = GetPositionFromCharIndex(index);
			s.y -= ((Height-60)/2)-currentPosition.Y;
			if (s.y>=0)
				ScrollPosition = s;
		}

		#region Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent() {
			components = new System.ComponentModel.Container();
		}
		#endregion
	}
}